/****************************************************************************
 * Copyright 2009 Huascar A. Sanchez                                        *
 *                                                                          *
 * Licensed under the Apache License, Version 2.0 (the "License");          *
 * you may not use this file except in compliance with the License.         *
 * You may obtain a copy of the License at                                  *
 *                                                                          *
 *     http://www.apache.org/licenses/LICENSE-2.0                           *
 *                                                                          *
 * Unless required by applicable law or agreed to in writing, software      *
 * distributed under the License is distributed on an "AS IS" BASIS,        *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
 * See the License for the specific language governing permissions and      *
 * limitations under the License.                                           *
 ****************************************************************************/
package com.gotobject.filter.common;

import com.gotobject.filter.FilterImpl;
import static com.gotobject.filter.common.Iterables.asCollection;
import static com.gotobject.filter.common.Iterables.*;

import com.gotobject.filter.predicate.Predicate;
import com.gotobject.filter.spi.FilteringImpl;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.Collections;

/**
 * @author huascar.sanchez@gmail.com (Huascar A. Sanchez)
 */
public class Aggregations {
    private Aggregations(){}

    public static <T> int count(Iterable<T> iterable){
        return new Count<T>(iterable).count();
    }

    public static <T> int count(Iterable<T> iterable, Predicate<? super T> cond){
        return new Count<T>(iterable).count(cond);
    }

    public static <T extends Object & Comparable<? super T>> T max(Iterable<? extends T> iterable){
        return new Max<T>(iterable).max();
    }

    public static <T extends Object & Comparable<? super T>> T max(Iterable<? extends T> iterable, Predicate<? super T> cond){
        return new Max<T>(iterable).max(cond);
    }


    public static <T extends Object & Comparable<? super T>> T min(Iterable<? extends T> iterable){
        return new Min<T>(iterable).min();
    }

    public static <T extends Object & Comparable<? super T>> T min(Iterable<? extends T> iterable, Predicate<? super T> cond){
        return new Min<T>(iterable).min(cond);
    }


    public static <T extends Number> int sum(Iterable<T> iterable){
        return new Sum<T>(iterable).sum();
    }

    public static <T> int sumChars(Iterable<T> iterable){
        return new Sum<T>(iterable).sumChars();
    }


    /**
     * @author huascar.sanchez@gmail.com (Huascar A. Sanchez)
     * @param <T>
     */
    public static class Count<T> {
       private final Iterable<T> aggregated;

       Count(Iterable<T> aggregated){
           this.aggregated = aggregated;
       }

        public int count(){
           return asCollection(aggregated).size();
       }

       public int count(Predicate<? super T> cond){
           final FilterImpl filter = new FilterImpl();
           try {
              return asCollection(new FilteringImpl<T>(filter).filter(cond, aggregated)).size();
           } finally {
               filter.throwFailedFilteringExceptions();
           }
       }

        @Override
        public String toString() {
            return "count:" + asList(aggregated);
        }
    }


    private static class Max<T extends Object & Comparable<? super T>> {
        private final Iterable<? extends T> aggregated;

        Max(Iterable<? extends T> aggregated){
            this.aggregated = aggregated;
        }

        public T max(){
            return Collections.max(asCollection(aggregated));
        }

        public T max(Predicate<? super T> cond){
           final FilterImpl filter = new FilterImpl();
           try {
              return Collections.max(asCollection(new FilteringImpl<T>(filter).filter(cond, capturedWildCard(aggregated))));
           } finally {
               filter.throwFailedFilteringExceptions();
           }
        }

        @Override
        public String toString() {
            return "max of:" + asList(aggregated);
        }
    }

    private static class Min<T extends Object & Comparable<? super T>> {
        private final Iterable<? extends T> aggregated;

        Min(Iterable<? extends T> aggregated){
            this.aggregated = aggregated;
        }

        public T min(){
            return Collections.min(asCollection(aggregated));
        }

        public T min(Predicate<? super T> cond){
           final FilterImpl filter = new FilterImpl();
           try {
              return Collections.min(asCollection(new FilteringImpl<T>(filter).filter(cond, capturedWildCard(aggregated))));
           } finally {
               filter.throwFailedFilteringExceptions();
           }
        }

        @Override
        public String toString() {
            return "min of:" + asList(aggregated);
        }
    }

    /**
     * @author huascar.sanchez@gmail.com (Huascar A. Sanchez)
     * @param <T>
     */
    public static class Sum<T> {
        private final Iterable<T> aggregated;
        Sum(Iterable<T> aggregated){
            this.aggregated = aggregated;
        }

        public int sum(){
            final AtomicInteger total = new AtomicInteger(0);
            for(T each : aggregated){
                total.getAndAdd(Integer.class.cast(each));
            }
            return total.get();
        }

        public int sumChars(){
            final AtomicInteger total = new AtomicInteger(0);
            for(T each : aggregated){
                total.getAndAdd(each.toString().length());
            }
            return total.get();
        }

        @Override
        public String toString() {
            return "sum of:" + asList(aggregated);
        }
    }
}
